#pragma once

#include <dirent.h>
#include <cstdio>
#include "../gl/Drawabe.h"
#include <GL/glut.h>
#include <cstring>
#include "model.h"

struct Color {
    GLbyte r;
    GLbyte g;
    GLbyte b;
    GLbyte a;
    Color(GLbyte r, GLbyte g, GLbyte b, GLbyte a = 127) {
        this->r = r;
        this->g = g;
        this->b = b;
        this->a = a;
    }
};

struct GraphicsOptions {
    struct {
        uint16_t size;
    } cell;
    struct {
        uint8_t line_width;
        Color color;
    } grid;
    struct {
        uint8_t width;
        Color color;
    } border;
    struct {
        Color bg;
        Color snake;
        Color fail_snake;
        Color pause_snake;
    } color;
};


class FoodView {
private:
    friend View;
    int count = 0;
    Sprite ** items = nullptr;
public:
    Sprite * item(int index) {
        return this->items[index];
    }

    void load() {
        auto is_valid_entry = [](dirent * entry) -> bool {
            return entry->d_name[0]!='.';
        };
        this->count = 0;
        char file_name[512];
        DIR * dir = opendir("res/foods/");
        while (dirent * entry = readdir(dir)) {
            if (is_valid_entry(entry))
                this->count++;
        }
        rewinddir(dir);
        this->items = (Sprite**)malloc(sizeof(void*) * this->count);
        int i = 0;
        while (dirent * entry = readdir(dir)) {
            if (is_valid_entry(entry)) {
                sprintf(file_name, "res/foods/%s", entry->d_name);
                this->items[i] = new Sprite();
                this->items[i]->load(file_name);
                i++;
            }
        }
        closedir(dir);
    }

    ~FoodView() {
        for (int i=0;i<this->count;i++)
            delete this->items[i];
        free(this->items);
    }
};


class SnakeView{
private:
    friend View;
    Sprite * head;
    Sprite * tail;
    Sprite * body;
    Sprite * corner;
public:
    SnakeView() {
        this->head = new Sprite();
        this->tail = new Sprite();
        this->body = new Sprite();
        this->corner = new Sprite();
    }

    void load() {
        this->head->load("res/snake/head.png");
        this->tail->load("res/snake/tail.png");
        this->body->load("res/snake/body.png");
        this->corner->load("res/snake/corner.png");
    }
};

class View {
private:
    FoodView * foods = nullptr;
    SnakeView * snake = nullptr;
    Textured * field = nullptr;
    GraphicsOptions * options;
    Game *game;
    float h_size{}, v_size{};
    GLuint grid_list_id{};
    GLuint border_list_id{};


    float xPos(int cell_id, bool widh_grid = true) {
        return float(cell_id * (this->options->cell.size + options->grid.line_width) + (widh_grid?options->grid.line_width:0));
    }

    float yPos(int cell_id, bool widh_grid = true) {
        return float (cell_id * (this->options->cell.size + options->grid.line_width) + (widh_grid?options->grid.line_width:0));
    }

    void draw_border() {
        glColor3f(1, 0, 0);
        glCallList(border_list_id);
        glColor3f(1, 1, 1);
    }

    void draw_grid() const {
        glColor4b(this->options->grid.color.r, this->options->grid.color.g, this->options->grid.color.b, this->options->grid.color.a);
        glCallList(grid_list_id);
        glColor4b(127, 127, 127, 127);
    }

    void draw_food() {
        ListItem * head = this->game->food->getHead();
        if (head== nullptr)
            return;
        ListItem * current = head;
        do {
            if (current->data == nullptr)
                current->data = this->foods->item((int)random() % this->foods->count);
            ((Sprite*)(current->data))->draw(xPos(current->point.x), yPos(current->point.y));
            current = current->tail;
        } while(head!=current);

    }

    void draw_snake() {
        Color * color;
        switch (this->game->state) {
            case GS_DONE:
                color = &this->options->color.fail_snake;
                break;
            case GS_PAUSE:
                color = &this->options->color.pause_snake;
                break;
            default:
                color = nullptr;
                break;
        }

        ListItem * head = this->game->snake->getHead();
        ListItem * current = head->tail;
        Sprite * sprite;
        GLfloat angle;
        do {
            bool angle_ok = false;
            MoveAction check_rotate_action;
            if (current==head) {
                sprite = this->snake->head;
                check_rotate_action = (MoveAction)(long)(current->data);
            } else if (current==head->tail) {
                sprite = this->snake->tail;
                check_rotate_action = (MoveAction)(long)(current->tail->data);
            } else {
                if (current->data==current->tail->data) {
                    sprite = this->snake->body;
                    check_rotate_action = (MoveAction)(long)(current->data);
                } else {
                    sprite = this->snake->corner;
                    auto cur = (MoveAction)long(current->data);
                    auto next = (MoveAction)long(current->tail->data);
                    if (cur==MoveAction::MV_RIGHT) {
                        angle = next==MoveAction::MV_DOWN ? 180 : -90;
                    } else if (cur==MoveAction::MV_LEFT) {
                        angle = next==MoveAction::MV_DOWN ? 90 : 0;
                    } else if (cur==MoveAction::MV_DOWN) {
                        angle = next==MoveAction::MV_RIGHT ? 0 : -90;
                    } else if (cur==MoveAction::MV_UP) {
                        angle = next == MoveAction::MV_RIGHT ? 90 : 180;
                    }
                    angle_ok = true;
                }
            }


            if (!angle_ok) {
                switch (check_rotate_action) {
                    case MoveAction::MV_LEFT: { angle = 180; break; }
                    case MoveAction::MV_UP: { angle = -90; break; }
                    case MoveAction::MV_DOWN: { angle = 90; break; }
                    default: { angle = 0; break; }
                }
            }

            if (color!= nullptr)
                glColor4b(color->r, color->g, color->b, color->a);
            glPushMatrix();

            glTranslatef(float(xPos(current->point.x))+float(this->options->cell.size)/2, float(yPos(current->point.y))+float(this->options->cell.size)/2, 0);
            glRotatef(angle, 0, 0, 1);
            sprite->draw(-float(this->options->cell.size)/2, -float(this->options->cell.size)/2);
            glPopMatrix();

            if (current==head)
                break;

            current = current->tail;
        } while(true);
        glColor4b(127, 127, 127, 127);
    }

public:

    View(GraphicsOptions &options, Game * game) {
        this->options = &options;
        this->game = game;
        this->foods = new FoodView();
        this->snake = new SnakeView();
    }


    void print_str(const char * str) {
        glRotatef(-180, 1.0, 0.0, 0.0);
        glScalef(0.2, 0.2, 0);
        glColor3d(64, 0, 0);
        glLineWidth(2);
        int i=0;
        while (*str) {
            glPushMatrix();
            glTranslatef(33*i*5, 0, 0);
            glutStrokeCharacter(GLUT_STROKE_ROMAN, *str);
            str++;
            i++;
            glPopMatrix();
        }
        glColor3d(127, 127, 127);
    }

    void draw() {
        glClear(GL_COLOR_BUFFER_BIT);
        glPushMatrix();
        this->draw_border();
        glTranslatef(options->border.width, options->border.width, 0);
        //glScalef(0.5, 0.5, 0.5);


        this->field->draw();
        this->draw_grid();
        this->draw_food();
        this->draw_snake();

        glPopMatrix();

        glPushMatrix();
        glTranslatef(25, yPos(game->options->y_sz + 2), 0);
        print_str("Hello world!");
        glPopMatrix();
    }

    void prepare() {
        h_size = float(game->options->x_sz * (this->options->cell.size + options->grid.line_width) + options->grid.line_width);
        v_size = float(game->options->y_sz * (this->options->cell.size + options->grid.line_width) + options->grid.line_width);

        this->field = new Field(float(h_size - float(options->grid.line_width)), float(v_size - float(options->grid.line_width)));
        this->field->load("res/field_bg.png");

        this->snake->load();
        this->foods->load();

        grid_list_id = glGenLists(2);
        border_list_id = grid_list_id + 1;
        glNewList(grid_list_id, GL_COMPILE);
        glBegin(GL_QUADS);
        for (int i=0; i<=game->options->y_sz;i++) {
            float y = yPos(i, false);
            glVertex2f(0, y);
            glVertex2f(h_size, y);
            glVertex2f(h_size, y+1);
            glVertex2f(0, y+1);
        }
        for (int i=0; i<=game->options->x_sz;i++) {
            float x = xPos(i, false);
            glVertex2f(x, 0);
            glVertex2f(x, v_size);
            glVertex2f(x + 1, v_size);
            glVertex2f(x + 1, 0);
        }
        glEnd();
        glEndList();

        glNewList(border_list_id, GL_COMPILE);
        glBegin(GL_QUADS);
        float y = yPos(this->game->options->y_sz, true) + options->border.width * 2;
        float x = xPos(this->game->options->x_sz, true) + options->border.width * 2;
        glVertex2f(0, 0);
        glVertex2f(options->border.width, 0);
        glVertex2f(options->border.width, y);
        glVertex2f(0, y);

        glVertex2f(0, 0);
        glVertex2f(x, 0);
        glVertex2f(x, options->border.width);
        glVertex2f(0, options->border.width);

        glVertex2f(x-options->border.width, 0);
        glVertex2f(x, 0);
        glVertex2f(x, y);
        glVertex2f(x-options->border.width, y);

        glVertex2f(0, y-options->border.width);
        glVertex2f(x, y-options->border.width);
        glVertex2f(x, y);
        glVertex2f(0, y);

        glEnd();
        glEndList();
    }

    ~View() {
        delete this->foods;
        delete this->field;
        delete this->snake;
    }
};